using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.IO;
using System.Text;

namespace flext
{
	public class Formatter
	{
		private int _CurrentLineIndex;
		private IList _Source; 
		private int _headingLevelBase;
		private WikiOutput _Output;
		bool NoFollowExternalHyperlinks=false;
		private string BehaviorDelimiter = "@@";

		public bool NoFollowHyperlinks
		{
			set{NoFollowExternalHyperlinks=value;}
		}

		public Formatter(string source)
		{
			_Source=SplitStringIntoStyledLines(source,LineStyle.Unchanged);
			_headingLevelBase=0;
		}

		public string Format()
		{
			_CurrentLineIndex = 0;
			//NoFollowExternalHyperlinks=false;
			bool inMultilineProperty = false;
			bool currentMultilinePropertyIsHidden = false;
			string multiLinePropertyDelim = null;
			bool inPreBlock=false;
			bool inExtendedPreBlock = false;
			string preBlockKey=null;
			CurrentState = new NeutralState(this);
			WikiOutput output=WikiOutput.ForFormat(OutputFormat.HTML,null);
			_Output=output;

			for (int lineNumber = 0; lineNumber < _Source.Count; lineNumber++)
			{
				StyledLine eachLine = (StyledLine)(_Source[lineNumber]);
				string each = eachLine.Text;
				_Output.Style = eachLine.Style;
				each=StripHTMLSpecialCharacters(each);

				if(inPreBlock)
				{
					if ((each.StartsWith("}+") || each.StartsWith("}@")) && each.Substring(2).Trim() == preBlockKey )
					{
						//Ensure(typeof(NeutralState));
						inPreBlock = false;
						preBlockKey = null;
					}
					else
					{
						if (false == currentMultilinePropertyIsHidden)
						{
							each = each.Replace("\t", "		");
							if (inExtendedPreBlock)
							{
								each = ProcessLineElements(each);
							}
							_Output.Write(each);
							_Output.WriteEndLine();
						}
						_CurrentLineIndex++;
					}
					continue;
				}
				else if ( !inMultilineProperty && (each.StartsWith("{+") || each.StartsWith("{@")) )
				{
					inPreBlock = true;
					inExtendedPreBlock = each.StartsWith("{+");
					preBlockKey = each.Substring(2).Trim();
					continue;
				}
				// Make all the 8-space sequences into tabs
				each = Regex.Replace(each, " {8}", "\t");

				// See if this is the first line of a multiline property.
				if (!inMultilineProperty && new Regex("^(?<leader>:?)(?<name>([A-Z_][_a-zA-Z0-9]+)):(?<delim>[\\[{])(?<val>.*)").IsMatch(each))
				{
					// OK, here we go -- time to output the header
					Match m = new Regex("^(?<leader>:?)(?<name>([A-Z_][_a-zA-Z0-9]+)):(?<delim>[\\[{])(?<val>.*)").Match(each);
					string name = m.Groups["name"].Value;
					string val = m.Groups["val"].Value;
					string leader = m.Groups["leader"].Value;
					string delim = m.Groups["delim"].Value;
					multiLinePropertyDelim = ClosingDelimiterForOpeningMultilinePropertyDelimiter(delim);
					currentMultilinePropertyIsHidden = leader == ":";
					if (currentMultilinePropertyIsHidden) // just write the anchor for hidden page properties
					{
						_Output.WriteOpenAnchor(name);
						_Output.WriteCloseAnchor();
						_Output.WriteLine("");
					}
					else
					{
						// Don't bother showing out hidden page properties
						val = val.Trim();

						// Do the normal processing
						name = StripHTMLSpecialCharacters(name);
						string linkedName = "abcLinkwikiname stripdown";

						val = StripHTMLSpecialCharacters(val);
						val = ProcessLineElements(val);

						_Output.WriteOpenProperty(linkedName);
						_Output.WriteOpenAnchor(name);
						if (IsBehaviorPropertyDelimiter(delim))
							_Output.Write(delim);
						_Output.Write(val);
					}
					inMultilineProperty = true;
					continue;
				}

				if (inMultilineProperty)
				{
					if (each.StartsWith(multiLinePropertyDelim))
					{
						// We're done!
						if (!currentMultilinePropertyIsHidden)
						{
							if (IsBehaviorPropertyDelimiter(multiLinePropertyDelim))
							{
								_Output.Write(multiLinePropertyDelim);
							}
							// Make sure we close off things like tables before we close the property.
							//Ensure(typeof(NeutralState));
							_Output.WriteCloseAnchor();
							_Output.WriteCloseProperty();
						}
						inMultilineProperty = false;
						currentMultilinePropertyIsHidden = false;
						continue;
					}

					// If it's a line for a property behavior, just show it -- don't perform any processing
					if (IsBehaviorPropertyDelimiter(multiLinePropertyDelim))
					{
						if (!currentMultilinePropertyIsHidden)
						{
							_Output.WriteSingleLine(each);
						}
						continue;
					}
				}

				// empty line resets everything (except pre and multiline properties )
				if (each.Trim().Length == 0)
				{
					if (!(CurrentState is PreState) || (CurrentState is PreState && !IsNextLinePre()))
						Ensure(typeof(NeutralState));
					_Output.WriteEndLine();
				}
				else if ((each.StartsWith("----")) && (false == currentMultilinePropertyIsHidden))
				{
					Ensure(typeof(NeutralState));
					_Output.WriteRule();
				}

				/* New topic removed */
				else if ((each.StartsWith(" ") || Regex.IsMatch(each, "^[ \t]+[^ \t*1]")) && 
					(false == currentMultilinePropertyIsHidden))
				{
					Ensure(typeof(PreState));
					_Output.Write(Regex.Replace(each, "\t", "        "));
				}
				else
				{
					// OK, it's likely more complicated

					// Continue if we're inside a multiline hidden property.
					if (true == currentMultilinePropertyIsHidden)
					{
						continue;
					}

					// See if this is a bullet line
					if (each.StartsWith("\t"))
					{
						each = ProcessLineElements(each);
						// Starts with a tab - might be a list (we'll see)
						// Count the tabs
						int thisNest = 0;
						while (each.Length > 0 && (each.StartsWith("\t")))
						{
							thisNest++;
							each = each.Substring(1);
						}

						if (each.StartsWith("*"))
						{
							each = each.Substring(1);
							// We're in a list - make sure we've got the right <ul> nesting setup
							// Could need more or fewer
							Ensure(typeof(UnorderedListState));
							((UnorderedListState)(CurrentState)).SetNesting(thisNest);
							_Output.WriteListItem(each);
						}
						else if (each.StartsWith("1."))
						{
							each = each.Substring(2);
							Ensure(typeof(OrderedListState));
							((OrderedListState)(CurrentState)).SetNesting(thisNest);
							_Output.WriteListItem(each);
						}
						else
						{
							// False alarm (just some tabs)
							_Output.Write(Regex.Replace(each, "\t", "        "));
						}
					}
					else if (each.StartsWith("||") && each.EndsWith("||") && each.Length >= 4)
					{
						bool firstRow = !(CurrentState is TableState);
						Ensure(typeof(TableState));
						TableState ts = (TableState)CurrentState;

						string endless = each.Substring(2, each.Length - 4);

						// Write the row
						bool firstCell = true;
						foreach (string eachCell in Regex.Split(endless, @"\|\|"))
						{
							string cellContent = eachCell;
							// Check the cell for formatting
							TableCellInfo info = new TableCellInfo();
							if (cellContent.StartsWith("{"))
							{
								int end = cellContent.IndexOf("}", 1);
								if (end != -1)
								{
									string fmt = cellContent.Substring(1, end - 1);
									cellContent = cellContent.Substring(end + 1);
									string result = info.Parse(fmt);
									if (result != null)
										cellContent = "(Error: " + result + ") " + cellContent;
								}
							}
							if (firstCell)
							{
								if (firstRow)
								{
									ts.HasBorder = info.HasBorder;
									Output.WriteOpenTable(info.TableAlignment, info.HasBorder, info.TableWidth);
								}
								_Output.WriteOpenTableRow();
							}
							_Output.WriteTableCell(ProcessLineElements(cellContent), info.IsHighlighted, info.CellAlignment, info.ColSpan, info.RowSpan, ts.HasBorder, info.AllowBreaks, info.CellWidth, info.BackgroundColor);
							firstCell = false;
						}
						_Output.WriteCloseTableRow();
					}
					else
					{
						Ensure(typeof(NeutralState));

						// See if we've got a heading prefix
						int heading = -1;
						if (each.StartsWith("!!!!!!!"))
							heading = 7;
						else if (each.StartsWith("!!!!!!"))
							heading = 6;
						else if (each.StartsWith("!!!!!"))
							heading = 5;
						else if (each.StartsWith("!!!!"))
							heading = 4;
						else if (each.StartsWith("!!!"))
							heading = 3;
						else if (each.StartsWith("!!"))
							heading = 2;
						else if (each.StartsWith("!"))
							heading = 1;

						if (heading != -1)
						{
							_Output.WriteHeading(ProcessLineElements(each.Substring(heading)), _headingLevelBase + heading);
						}
						else
						{
							// If it's a single-line field, wrap it visually
							if (new Regex("^(?<leader>:?)(?<name>([A-Z_][_a-zA-Z0-9.]+)):(?<val>[^\\[{].*)").IsMatch(each))
							{
								Match m = new Regex("^(?<leader>:?)(?<name>([A-Z_][_a-zA-Z0-9.]+)):(?<val>[^\\[{].*)").Match(each);
								string name = m.Groups["name"].Value;
								string val = m.Groups["val"].Value;
								string leader = m.Groups["leader"].Value;
								bool isLeader = leader == ":";

								// Write out an anchor tag.
								if (isLeader)	// Only bother writing anchor and name for hidden page properties
								{
									_Output.WriteOpenAnchor(name);
									_Output.WriteCloseAnchor();
								}
								else
								{
									// Do the normal processing

									string linkedName = "stripted LinkWikiNames(name)";

									val = val.Trim();
									val = ProcessLineElements(val);

									_Output.WriteOpenProperty(linkedName);
									_Output.WriteOpenAnchor(name);
									_Output.Write(val);
									_Output.WriteCloseAnchor();
									_Output.WriteCloseProperty();
								}
							}
							else
							{
								// As vanilla as can be -- just send it along
								each = ProcessLineElements(each);
								EnsureParaOpen();
								_Output.Write(each);
							}
						}
					}
				}

				EnsureParaClose();
				if (false == currentMultilinePropertyIsHidden)
				{
					_Output.WriteEndLine();
				}
				_CurrentLineIndex++;
			}
    
			_Output.End();

			CurrentState = null;
			return _Output.ToString();
		}


		#region some imp methods
		
		private string ClosingDelimiterForOpeningMultilinePropertyDelimiter(string open)
		{
			switch (open)
			{
				case "[":
					return "]";
				case "{":
					return "}";
			}
			throw new Exception("Illegal multiline property delimiter.");
		}
		private bool IsBehaviorPropertyDelimiter(string s)
		{
			return s == "{" || s == "}";
		}

		private string StripHTMLSpecialCharacters(string input)
		{
			// replace HTML special characters with character entities
			// this has the side-effect of stripping all markup from text
			string str = input;
			// Special care needs to be taken to be sure & is not stripped out of URLs
			str = Regex.Replace (str, " &", " &amp;") ;

			str = Regex.Replace (str, "<", "&lt;") ;
			str = Regex.Replace (str, ">", "&gt;") ;
			return str;
		}

		#endregion
		
		#region ConvertEmoticons
		private string ConvertEmoticons(string input)
		{
			// organized so that we can shortcut misses as much as possible
			string str = input;
			if (str.IndexOf("(") > -1)
			{
				str = Emote(str, "(Y)", "emoticons/thumbs_up.gif");
				str = Emote(str, "(y)", "emoticons/thumbs_up.gif");
				str = Emote(str, "(N)", "emoticons/thumbs_down.gif");
				str = Emote(str, "(n)", "emoticons/thumbs_down.gif");
				str = Emote(str, "(B)", "emoticons/beer_yum.gif");
				str = Emote(str, "(b)", "emoticons/beer_yum.gif");
				str = Emote(str, "(D)", "emoticons/martini_shaken.gif");
				str = Emote(str, "(d)", "emoticons/martini_shaken.gif");
				str = Emote(str, "(X)", "emoticons/girl_handsacrossamerica.gif");
				str = Emote(str, "(x)", "emoticons/girl_handsacrossamerica.gif");
				str = Emote(str, "(Z)", "emoticons/guy_handsacrossamerica.gif");
				str = Emote(str, "(z)", "emoticons/guy_handsacrossamerica.gif");
				str = Emote(str, "(6)", "emoticons/devil_smile.gif");
				str = Emote(str, "(})", "emoticons/girl_hug.gif");
				str = Emote(str, "({)", "emoticons/dude_hug.gif");
				str = Emote(str, "(H)", "emoticons/shades_smile.gif");
				str = Emote(str, "(h)", "emoticons/shades_smile.gif");
				str = Emote(str, "(A)", "emoticons/angel_smile.gif");
				str = Emote(str, "(a)", "emoticons/angel_smile.gif");
				str = Emote(str, "(L)", "emoticons/heart.gif");
				str = Emote(str, "(l)", "emoticons/heart.gif");
				str = Emote(str, "(U)", "emoticons/broken_heart.gif");
				str = Emote(str, "(u)", "emoticons/broken_heart.gif");
				str = Emote(str, "(K)", "emoticons/kiss.gif");
				str = Emote(str, "(k)", "emoticons/kiss.gif");
				str = Emote(str, "(G)", "emoticons/present.gif");
				str = Emote(str, "(g)", "emoticons/present.gif");
				str = Emote(str, "(F)", "emoticons/rose.gif");
				str = Emote(str, "(f)", "emoticons/rose.gif");
				str = Emote(str, "(W)", "emoticons/wilted_rose.gif");
				str = Emote(str, "(w)", "emoticons/wilted_rose.gif");
				str = Emote(str, "(P)", "emoticons/camera.gif");
				str = Emote(str, "(p)", "emoticons/camera.gif");
				str = Emote(str, "(~)", "emoticons/film.gif");
				str = Emote(str, "(T)", "emoticons/phone.gif");
				str = Emote(str, "(t)", "emoticons/phone.gif");
				str = Emote(str, "(@)", "emoticons/kittykay.gif");
				//			str = Emote(str, "(&)", "emoticons/bowwow.gif");	// disabled because it conflicts with lots of formatting rules
				str = Emote(str, "(C)", "emoticons/coffee.gif");
				str = Emote(str, "(c)", "emoticons/coffee.gif");
				str = Emote(str, "(I)", "emoticons/lightbulb.gif");
				str = Emote(str, "(i)", "emoticons/lightbulb.gif");
				str = Emote(str, "(S)", "emoticons/moon.gif");
				str = Emote(str, "(*)", "emoticons/star.gif");
				str = Emote(str, "(8)", "emoticons/musical_note.gif");
				str = Emote(str, "(E)", "emoticons/envelope.gif");
				str = Emote(str, "(e)", "emoticons/envelope.gif");
				str = Emote(str, "(^)", "emoticons/cake.gif");
				str = Emote(str, "(O)", "emoticons/clock.gif");
				str = Emote(str, "(o)", "emoticons/clock.gif");
				str = Emote(str, "(M)", "emoticons/messenger.gif");
				str = Emote(str, "(m)", "emoticons/messenger.gif");
			}
			if (str.IndexOf(":") > -1)
			{
				str = Emote(str, ":-[", "emoticons/bat.gif");
				// str = Emote(str, ":[", "emoticons/bat.gif");  // not supported because it conflicts with the topic property syntax
				str = Emote(str, ":-)", "emoticons/regular_smile.gif");
				str = Emote(str, ":)", "emoticons/regular_smile.gif");
				str = Emote(str, ":-D", "emoticons/teeth_smile.gif");
				// str = Emote(str, ":d", "emoticons/teeth_smile.gif"); // conflicts with urls (e.g., mailto:)
				str = Emote(str, ":-O", "emoticons/omg_smile.gif");
				// str = Emote(str, ":o", "emoticons/omg_smile.gif");  // conflicts with urls (e.g., mailto:)
				str = Emote(str, ":-P", "emoticons/tounge_smile.gif");
				// str = Emote(str, ":p", "emoticons/tounge_smile.gif");  // conflicts with urls (e.g., mailto:)
				str = Emote(str, ":-(", "emoticons/sad_smile.gif");
				str = Emote(str, ":(", "emoticons/sad_smile.gif");
				str = Emote(str, ":-S", "emoticons/confused_smile.gif");
				// str = Emote(str, ":s", "emoticons/confused_smile.gif");  // conflicts with urls (e.g., mailto:)
				str = Emote(str, ":-|", "emoticons/whatchutalkingabout_smile.gif");
				str = Emote(str, ":|", "emoticons/whatchutalkingabout_smile.gif");
				str = Emote(str, ":'(", "emoticons/cry_smile.gif");
				str = Emote(str, ":$", "emoticons/embaressed_smile.gif");
				str = Emote(str, ":-$", "emoticons/embaressed_smile.gif");
				str = Emote(str, ":-@", "emoticons/angry_smile.gif");
				str = Emote(str, ":@", "emoticons/angry_smile.gif");
			}
			if (str.IndexOf(";") > -1)
			{
				str = Emote(str, ";-)", "emoticons/wink_smile.gif");
				str = Emote(str, ";)", "emoticons/wink_smile.gif");
			}
			return str;
		}
		#endregion

		#region Emote
		private string Emote(string input, string text, string image)
		{
			// there's a fair bit of processing to skip if we can
			if (input.IndexOf(text) > -1)
			{
				LinkMaker lm=new LinkMaker();
				string img = string.Format("<img src=\"{0}\"/>", lm.LinkToImage(image));
				return input.Replace(text, img);
			}
			return input;
		}
		#endregion

		#region SplitStringIntoStyledLines
		private IList SplitStringIntoStyledLines(string input, LineStyle aStyle)
		{
			string sp;
			ArrayList answer = new ArrayList();
			
			sp = input.TrimEnd(new char[]{'\n'});
			sp = sp.Replace("\r", "");
			sp = sp.TrimEnd(new char[]{'\n'});
			foreach (string s in sp.Split (new char[]{'\n'}) )
				answer.Add(new StyledLine(s, aStyle));
			return MergeStyledBehaviorLines(answer);
		}
		private IList MergeStyledBehaviorLines(IList input)
		{
			ArrayList answer = new ArrayList();
			// We do a fancy thing here.  If there is an unterminated behavior that starts on a line, we suck up all following 
			// lines until we get the one that has the closing terminator.  We then combine them into one.
			//
			// Subtlties:
			//   - We upgrade any line styles, so that the new compound line gets the highest priority style found across all the lines

			for (int lineNumber = 0; lineNumber < input.Count; lineNumber++)
			{
				StyledLine eachLine = (StyledLine)(input[lineNumber]);
				int thisLineDelimiterCount = CountOccurrences(eachLine.Text, BehaviorDelimiter);
				if ((thisLineDelimiterCount  % 2) != 0)
				{
					while (true)
					{
						lineNumber++;
						if (lineNumber >= input.Count)
							break;
						StyledLine nextLine = (StyledLine)(input[lineNumber]);
						eachLine = eachLine.CombinedWith(nextLine);
						if (nextLine.Text.IndexOf(BehaviorDelimiter) != -1)
							break;
					}
				}
				answer.Add(eachLine);
			}
			return answer;
		}

		#endregion
		
		#region ProcessLineElements
		private readonly Regex EscapedTextRegex = new Regex(@"(@@.*?@@)|(\" + PreParam + @"\d+" + PostParam + @")|("""".*?"""")", RegexOptions.Compiled | RegexOptions.Singleline);
		private readonly Regex BehaviorRegex = new Regex(@"(@@.*?@@)", RegexOptions.Compiled | RegexOptions.Singleline);
		private string ProcessLineElements(string line)
		{
			// Break the line into parts, recognizing escape quotes ("")
			StringBuilder builder = new StringBuilder();
			// Convert foo@bar external ref syntax to standardized InterWiki behavior
			// This must happen here because the actual interpretation of these is as a behavior
			//line = externalWikiRef.Replace(line, new MatchEvaluator(externalWikiRefMatch));
			
			ArrayList parameters = new ArrayList();
			MatchCollection matches; // temp var used in several places in this method
			int index = 0; // temp var used in several places in this method

			#region GatherEscapedText
			matches = EscapedTextRegex.Matches(line);
			if (matches.Count > 0)
			{
				builder.Length = 0;
				index = 0;
				foreach (Match match in matches)
				{
					builder.Append(line.Substring(index, (match.Index - index)));
					// skip behaviors for now. We match them so that the Regex engine does the work
					// of dealing with @@""@@ and ""@@"" type things. Behaviors handled in the next block
					if (match.Groups[1].Success)
					{
						builder.Append(match.Value);
						index = match.Index + match.Length;
						continue;
					}
					if (match.Value.StartsWith(PreParam))
						Parameterize(builder, parameters, match.Value);
					else 
						Parameterize(builder, parameters, match.Value.Substring(2, match.Length - 2 - 2));

					index = match.Index + match.Length;
				}
				builder.Append(line.Substring(index));
				line = builder.ToString();
			}
			#endregion

			#region TranslateBehaviors
			string escapedTwo = "\\@\\@";
			matches = BehaviorRegex.Matches(line);
			if (matches.Count > 0)
			{
				builder.Length = 0; // reset
				index = 0;
				foreach (Match match in matches)
				{
					builder.Append(line.Substring(index, (match.Index - index)));
					if (!match.Groups[1].Success)
					{
						builder.Append(match.Value);
						index = match.Index + match.Length;
						continue;
					}
					// OK, we have one
					string expr = match.Value.Substring(2, match.Value.Length - (2 * 2));
					// Turn any escape sequences into unescaped @@
					expr = expr.Replace(escapedTwo, BehaviorDelimiter);
					//TopicContext tc = new TopicContext(TheFederation, ContentBase, new TopicInfo(TheFederation, CurrentTopic));
//					BehaviorInterpreter interpreter = new BehaviorInterpreter(CurrentTopic == null ? "" : CurrentTopic.Fullname, expr, TheFederation, TheFederation.WikiTalkVersion, this);
					string replacement = null;
//					if (!interpreter.Parse())
//					{	// parse failed
//						replacement = ErrorMessage(null, interpreter.ErrorString);
//					}
//					else
//					{	// parse succeeded
//						if (!interpreter.EvaluateToPresentation(tc, _ExternalWikiMap))
//						{	// eval failed
//							replacement = ErrorMessage(null, interpreter.ErrorString);
//						}
//						else
//						{	// eval succeeded
//							WikiOutput nOut = WikiOutput.ForFormat(Output.Format, Output);
//							interpreter.Value.OutputTo(nOut);
//							replacement = nOut.ToString();
//						}
//						foreach (CacheRule r in interpreter.CacheRules)
//							AddCacheRule(r);
//					}
					Parameterize(builder,parameters,replacement);
					index = match.Index + match.Length;
				}
				if (index < line.Length)
					builder.Append(line.Substring(index));
					
				line = builder.ToString();
			}
			#endregion
			line = LinkHyperLinks(line);
			matches = Regex.Matches(line,urlPattern);
			// hyperlinks are exempt from any emoticons or textile or anything else.
			if (matches.Count > 0)
								
			{
				builder.Length = 0;
				index = 0;
				foreach (Match m in matches)
															   				
				{
					builder.Append(line.Substring(index,(m.Index-index)));
					Parameterize(builder,parameters,line.Substring(m.Index,m.Length));
					index = m.Index+m.Length;
				}
				builder.Append(line.Substring(index));
				line = builder.ToString();
			}
			line = ConvertEmoticons(line);	// needs to come before textile because of precedence in overlappign formatting rules
			line = SentencePairedDelimiters(line);
			line = TextileFormat(line,parameters);
			line = ColorsEtcFormat(line);
			matches = Regex.Matches(line,@"(?:\<nowiki\>)(\<a.*?\<\/a\>)");
			if (matches.Count > 0)
			{
				builder.Length = 0;
				index = 0;
				foreach (Match m in matches)
															   				
				{
					Group g = m.Groups[1];
					builder.Append(line.Substring(index,(m.Index-index)));
					Parameterize(builder, parameters, line.Substring(g.Index, g.Length));
					index = m.Index+m.Length;
				}
				builder.Append(line.Substring(index));
				line = builder.ToString();
			}
			//line = LinkWikiNames(line);
			//line = ProcessWikiLinks(line);
			line = ReplaceParameters(line,parameters);
			return line;

		}

		/// <summary>
		/// Escape the given string for < > " and & HTML characters.
		/// </summary>
		/// <param name="input"></param>
		/// <returns>The new string</returns>
		static private string EscapeHTML(string input)
		{
			return escape(input);
		}

		static protected string escape(string input) 
		{
			if (input == null)
				return "";
			// replace HTML special characters with character entities
			// this has the side-effect of stripping all markup from text
			string str = input;
			str = Regex.Replace (str, "&", "&amp;") ;
			str = Regex.Replace (str, "\"", "&quot;") ;
			str = Regex.Replace (str, "<", "&lt;") ;
			str = Regex.Replace (str, ">", "&gt;") ;
			return str;
		}

		
		#region Output
		/// <summary>
		/// Answer the object to which output is being written
		/// </summary>
		private Formatter.WikiOutput Output
		{
			get
			{
				return _Output;
			}
		}
		#endregion
		#endregion

		#region ObfuscatableLinkReplacementPattern
		private string ObfuscatableLinkReplacementPattern(string replacementText, string replacementURL)
		{
			string noFollow = "";
			if (NoFollowExternalHyperlinks)
				noFollow = " rel=\"nofollow\" ";
			return @"<a " + noFollow + @"href=""" + replacementURL + @""">" + replacementText + @"</a>";
		}
		#endregion

		#region LineProcessingParameters
		private const string PreParam = "${";
		private const string PostParam = "}";
		
		private readonly Regex ParametersRegex = new Regex(@"\"+PreParam+@"\d+"+PostParam,RegexOptions.Compiled);

		private void Parameterize(StringBuilder sb, ArrayList parameters, string parameter)
		{
			sb.Append(PreParam + parameters.Count + PostParam);
			parameters.Add(parameter);
		}

		private string ReplaceParameters(string str, ArrayList parameters)
		{
			for (int i = parameters.Count-1; i >= 0; i--)
			{
				string tok = PreParam + i + PostParam;
				int pos = str.IndexOf(tok);
				if (pos == -1) continue;
				str = str.Substring(0, pos) + parameters[i] + str.Substring(pos + tok.Length);
				parameters.RemoveAt(i);
			}
			return str;
		}
		#endregion

		#region LinkHyperLinks
		private string LinkHyperLinks (string input)
		{
			string str = string.Empty;
			str = ProcessExternalRelabledLinks(input);
			if (str == string.Empty)
				str = input;

			// image links
			str = Regex.Replace (str, @"([^""']|^)http(://\S*(?i:\.jpg|\.gif|\.png|\.jpeg))",
				"$1<img src=\"HTTPIMAGESOURCE:$2\"/>") ;
			str = Regex.Replace (str, @"([^""']|^)https(://\S*(?i:\.jpg|\.gif|\.png|\.jpeg))",
				"$1<img src=\"HTTPSIMAGESOURCE:$2\"/>") ;
			// nbjones - removed \.doc|\.xls|\.ppt|\.txt from the regex (added in version 1.16) 
			// because it was causing bug 1309039.
			// the removal does not cause any unit tests to break and I can't see why it was here.
			str = Regex.Replace (str, @"([^""'])file(://\S*(?i:\.jpg|\.gif|\.png|\.jpeg))",
				"$1<img src=\"FILEIMAGESOURCE:$2\"/>") ;

			// web links (including those surrounded by parens, brackets and curlies)
			str = Regex.Replace (str, @"[(]" + urlPattern + "[)]", "(" + ObfuscatableLinkReplacementPattern("$1", "$1") + ")") ;
			str = Regex.Replace (str, @"[{]" + urlPattern + "[}]", "{" + ObfuscatableLinkReplacementPattern("$1", "$1") + "}") ;
			str = Regex.Replace (str, @"(^|\s)" + urlPattern, "$1" + ObfuscatableLinkReplacementPattern("$2", "$2"));
			str = Regex.Replace (str, @"[\\[]" + urlPatternInBrackets + "[\\]]", ObfuscatableLinkReplacementPattern("$1", "$1")) ;
			if (str != input)
			{
				int start = str.IndexOf("href=\"");;
				int len = 0;
				while (start > 0)
				{
					len = str.IndexOf("\"", start+6) - start;
					if (len > 0)
					{
						string hrefUri = str.Substring(start, len);
						str = str.Replace(hrefUri, hrefUri.Replace(" ", "%20"));
						start = str.IndexOf("href=\"", start+len+1);
					}
					else
					{
						break;
					}
				}
			
			}

			// mail and news links (including those surrounded by parens, brackets and curlies)
			str = Regex.Replace (str, @"[(]" + mailAndNewsPattern + "[)]", "(" + ObfuscatableLinkReplacementPattern("${uri}", "${uri}") + ")") ;
			str = Regex.Replace (str, @"[{]" + mailAndNewsPattern + "[}]", "{" + ObfuscatableLinkReplacementPattern("${uri}", "${uri}") + "}") ;
			str = Regex.Replace (str, @"[\\[]" + mailAndNewsPattern + "[\\]]", "[" + ObfuscatableLinkReplacementPattern("${uri}", "${uri}") + "]") ;
			MatchCollection matches = Regex.Matches(str, mailAndNewsPattern);
			foreach (Match match in matches)
			{
				Group group = match.Groups["ignore"];
				if (null != group)
				{
					if ((0 == group.Captures.Count) || 
						((group.Captures.Count > 0) && (false == group.Captures[0].Value.EndsWith("\"")) && 
						(false == group.Captures[0].Value.EndsWith(">"))))
					{
						str = Regex.Replace (str, mailAndNewsPattern, ObfuscatableLinkReplacementPattern("${uri}", "${uri}"));
					}
				}
			}
			str = FinalizeImageLinks(str);
			return str;
		}
		#endregion
		
		#region ProcessExternalRelabledLinks
		private string ProcessExternalRelabledLinks(string input)
		{
			StringBuilder answer = new StringBuilder();
			string str = input;

			while (str.Length > 0)
			{
				Match m = extractUrlLinks.Match(str);
				if (!m.Success)
				{
					break;
				}

				string uri = m.Groups["uri"].ToString();
				string before = m.Groups["before"].ToString();
				string after = m.Groups["after"].ToString();
				string relabel = m.Groups["relabel"].ToString();
				string anchor = m.Groups["anchor"].ToString();
				string tooltip = m.Groups["tooltip"].ToString();

				if (relabel != string.Empty)
				{
					uri = uri.Replace("[", "");
					uri = uri.Replace("]", "");
					uri = uri.Replace(" ", "%20");
					if (anchor != string.Empty)
					{
						uri += "#" + anchor;
					}

					string noFollow = " ";
					if (NoFollowExternalHyperlinks)
					{
						noFollow = " rel=\"nofollow\" ";
					}
					if (tooltip != string.Empty)
					{
						tooltip = "title=\"" + tooltip + "\" ";
					}

					str = ReplaceMatch(answer, str, m, before + "<nowiki><a " + noFollow + tooltip + "href=\"" + uri + "\">" + relabel + "</a>" + after);
					//str = ReplaceMatch(answer, str, m, before + "<a class=\"externalLink\"" + noFollow + "href=\"" + uri + "\">" + relabel + "</a>" + after);
				}
				else
				{
					break;
				}

			}
			answer.Append(str);
			return answer.ToString();        
		}
		#endregion

		#region ReplaceMatch 
		private string ReplaceMatch (StringBuilder resultQueue, string old, Match m, string rep)
		{
			resultQueue.Append(old.Substring(0, m.Index) + rep);
			return old.Substring(m.Index + m.Length);
		}
		#endregion

		#region FinalizeImageLinks
		private string FinalizeImageLinks(string input)
		{
			// finalise image links
			string str = input;
			str = Regex.Replace (str, "HTTPIMAGESOURCE::(//\\S*)", "http:$1") ;
			str = Regex.Replace (str, "HTTPSIMAGESOURCE::(//\\S*)", "https:$1") ;
			str = Regex.Replace (str, "FILEIMAGESOURCE::(//\\S*)", "file:$1") ;
			return str;
		}
		#endregion

		#region Regular Expressions

		#region Unicode compatible regex fragments.
		// Lu: Letter Uppercase
		// Ll: Letter Lowercase
		// Lt: Letter Title
		// Lo: Letter Other
		// Nd: Number decimal
		// Pc: Punctation connector
		public static string AZ = "[\\p{Lu}]"; // A-Z
		public static string az09 = "[\\p{Ll}\\p{Lt}\\p{Lo}\\p{Nd}]"; // a-z0-9
		public static string Az09 = "[\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lo}\\p{Nd}]"; // A-Za-z0-9
		#endregion

		#region WikiName regexs
		// A wiki name is a topic name, possibly with a namespace
		// string patterns for:
		//		* what comes before a wiki name
		//		* what comes after a wiki name    
		//		* the optional anchor after a wiki name
		//		* legal namespace names
		// etc...
		public static string beforeWikiName = "(?<before>^|\\||\\*|'|\\s|\\(|\\{|\\!|\\>)";
		public static string afterWikiName = "(?<after>'|\\||\\s|@|$|\\.|,|:|'|;|\\}|\\?|_|\\)|\\!|\\<)";
		public static string wikiNameAnchor = @"(?:\#)?(?<anchor>([\w\d]+))?";
		private static string namespaceName = AZ + "[\\w]+";
		private static string startsWithMulticaps = "(" + AZ + "{2,}" + az09 + "+" + Az09 + "*)";
		private static string startsWithOneCap = "(" + AZ + az09 + "+" + Az09 + "*)";
		private static string unbracketedWikiName = "(?:_?" + startsWithMulticaps + "|" + startsWithOneCap + ")";
		private static string bracketedWikiName = "\\[(?:[\\w ]+)\\]"; // \w is a word character (A-z0-9_)
		private static string unqualifiedWikiName = "(?:" + "(?:" + unbracketedWikiName + ")|(?:" + bracketedWikiName + ")" + ")";
		private static string qualifiedWikiName = "(?:" + namespaceName + "\\.)*"  + unqualifiedWikiName;
		private static string forcedLocalWikiName = "\\."  + unqualifiedWikiName;
		public static string wikiName = "(?:" + "(?:" + qualifiedWikiName + ")|(?:" + unqualifiedWikiName + ")|(?:" + forcedLocalWikiName + ")" + ")";

		public static string extractWikiNamesString = beforeWikiName + "(?<topic>" + wikiName + ")"  + afterWikiName;
		public static Regex extractWikiNames = new Regex(extractWikiNamesString) ;
		#endregion

		#region WikiLink regexs
		// A wiki link is a wiki name, possible prefixed by a relabel
		private static string relabelPrefix = "(\"(?<relabel>[^\"]+)\"\\:)";
		private static string beforeOrRelabel = "(" + relabelPrefix + "|" + beforeWikiName + ")";
		public static string extractWikiLinksString = beforeOrRelabel + "(?<topic>" + wikiName + ")"  + wikiNameAnchor + afterWikiName;
		public static Regex extractWikiLinks = new Regex(extractWikiLinksString) ;
		#endregion

		#region eMail Address Regexs
		private static string emailAddressString = @"([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)";
		private static Regex emailAddress = new Regex(emailAddressString);
		#endregion

		#region URL Regexs
		static string urlPattern = @"((https?|http|ftp|gopher|telnet|file|notes|ms-help):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+-=\\\.&]*)";
		static string urlPatternInBrackets = @"((https?|ftp|gopher|telnet|file|notes|ms-help):((//)|(\\\\))+[\w\d:#@%/;$() ~_?\+-=\\\.& ]*)";
		static string mailAndNewsPattern = @"(?<ignore>(^|\s|\W)[\W*])?(?<uri>((mailto|news){1}:[^(//\s,)][\w\d:#@%/;$()~_?\+-=\\\.&]+))";

		private static string bracketedUrlPattern = "\\[(?:"+ urlPatternInBrackets + ")\\]";
		private static string bracketedmailAndNewsPattern = "\\[(?:"+ mailAndNewsPattern + ")\\]";
		private static string unqualifiedUrlPattern = "(?:" + "(?:" + bracketedUrlPattern + ")|(?:" + urlPattern + ")" + ")|(?:" + mailAndNewsPattern + ")|(?:" + bracketedmailAndNewsPattern + ")";
		
		private static string mustHaverelabelPrefix = "(\"(?<relabel>[^\"]+?)\\s*?(?:\\((?<tooltip>.*?)\\))?\"\\:)";
		private static string urlbeforeOrRelabel = "(" + mustHaverelabelPrefix + "|(" + mustHaverelabelPrefix + "|" + beforeWikiName + "))";
		public static string extractUrlLinksString = mustHaverelabelPrefix + "(?<uri>" + unqualifiedUrlPattern + ")" + wikiNameAnchor + afterWikiName;
		public static Regex extractUrlLinks = new Regex(extractUrlLinksString) ;
		#endregion
		
		#endregion

		#region TextileFormat
		private Regex codeRegexEsc = new Regex(@"(^|\s|\(|\[)@(\"+PreParam+"[^@]+"+PostParam+")@", RegexOptions.Compiled);
		private Regex codeRegexLink = new Regex(@"(^|\s|\(|\[)@\[([^@]+)\]@", RegexOptions.Compiled);
		private Regex codeRegexWikiLink = new Regex(@"(^|\s|\(|\[)@("+wikiName+")@", RegexOptions.Compiled);
		private Regex codeRegex = new Regex(@"(^|\s|\(|\[)@([^@]+)@", RegexOptions.Compiled);
		private string TextileFormat(string input, ArrayList parameters)
		{
			// NOTE: This code now expects for the input string to not contain any URLs. These should have
			// been Parameterize()d before this stage.
			string str = input;
			// _emphasis_
			str = Regex.Replace (str, "(^|\\W)_([^ ].*?)_", "$1<em>$2</em>") ;
			// *strong* 
			str = Regex.Replace (str, "(^|\\W)\\*([^ ].*?)\\*", "$1<strong>$2</strong>") ;
			// ??citation?? 
			str = Regex.Replace (str, "(^|\\W)\\?\\?([^ ].*?)\\?\\?", "$1<cite>$2</cite>") ;
			// -deleted text- 
			// Special care needs to be taken with this expression so that the - in URLs and images does not get replaced
			// and that Wiki Signatures with trailing dates contianing hypens are ignored.
			str = Regex.Replace (str, "(^|\\s+)-([^ -].*?)-", "$1<del>$2</del>");
			// +inserted text+ 
			str = Regex.Replace (str, "(^|\\W)\\+(.*?)\\+", "$1<ins>$2</ins>") ;
			// ^superscript^ 
			str = Regex.Replace (str, "(^|\\W)\\^([^ ].*?)\\^", "$1<sup>$2</sup>") ;
			// ~subscript~ 
			// Special care needs to be taken with this expression so that the ~ in URLs does not get replaced.
			str = Regex.Replace (str, "(^|\\W)~([^ ].*?)~", "$1<sub>$2</sub>") ;
			// @code@ 
			// The regex is a bit special because it needs to start with a zero-width negative lookbehind assertion (because we 
			// need to be sure we don't consume WikiBehaviors @@Whatever@@
			// Code is divided into three regexes. We are treating @ @ different than the rest of the textiles in that
			// @ @ is also to mean to escape the text inside.
			// This first one is for cases where we are saying "Link this!"
			if (codeRegexLink.Match(str).Success) str = codeRegexLink.Replace(str,"$1<code>[$2]</code>");
			// This is one that matches a potential WikiTopic
			MatchCollection matches = codeRegexWikiLink.Matches(str);
			if (matches.Count > 0)
			{
				// We want to consume all the matches and treat them as escaped parameters
				StringBuilder sb = new StringBuilder();
				int index = 0;
				for (int i = 0; i < matches.Count; i++)
				{
					Match m = matches[i];
					Group g = m.Groups[2];
					sb.Append(str.Substring(index,(m.Index-index)));
					sb.Append(m.Groups[1].Value);
					sb.Append("<code>");
					Parameterize(sb,parameters,str.Substring(g.Index,g.Length));
					sb.Append("</code>");
					index = m.Index+m.Length;
				}
				sb.Append(str.Substring(index));
				str = sb.ToString();
			}
			// finally, this one automatically applies to everything else
			str = codeRegex.Replace(str,"$1<code>$2</code>");
			//			// "link text":url 
			//			str = Regex.Replace (str, "\"([^\"(]+)( \\(([^\\)]+)\\))?\":" + urlPattern, ObfuscatableLinkReplacementPattern("$1", "$4"));
			//			// "link text":url for mail and news
			//			str = Regex.Replace (str, "\"([^\"(]+)( \\(([^\\)]+)\\))?\":" + mailAndNewsPattern, ObfuscatableLinkReplacementPattern("$1", "${uri}"));

			return str;
		}
		#endregion

		#region ColorsEtcFormat
		string ColorsEtcFormat( string	input	)
		{
			string str = input;
			// "%([a-zA-Z]+)(	*([a-zA-Z]+))*%"
			bool insideSpan=false;
			int	insideBig=0;
			int	insideSmall=0;

			string elem="([a-zA-Z]*|#[a-fA-F0-9]{6})";
			Regex	style=new	Regex( "%"+elem+"( +"+elem+")*%" );
			Match	match;
			int	lastMatchedPosition=0;
			while	(	(match=style.Match(	str, lastMatchedPosition )).Success	)
			{
				string newValue="";

				// terminate previous	<big>	first	(if	any)
				for	(	;	insideBig	>	0; --insideBig )
				{
					newValue +=	"</big>";
				}

				// terminate previous	<small>	first	(if	any)
				for	(	;	insideSmall	>	0; --insideSmall )
				{
					newValue +=	"</small>";
				}

				// terminate previous	<span> first (if any)
				if ( insideSpan	)
				{
					newValue +=	"</span>";
					insideSpan = false;
				}

				if ( match.Groups[1].Length	>	0	)	// start new block
				{
					for	(	int	i=1; i < match.Groups.Count; i +=	2	)
					{
						string param=match.Groups[i].Value;
						if ( string.Compare( param,	"big", true	)	== 0 )
						{
							++insideBig;
							newValue +=	"<big>";
						}
						else if	(	string.Compare(	param, "small",	true ) ==	0	)
						{
							++insideSmall;
							newValue +=	"<small>";
						}
						else if	(	param	!= string.Empty	)
						{
							if ( insideSpan	)
							{
								newValue
									=	"<span class=\"errormessage\">"
									+		"<span class=\"errormessagetitle\">	Style	Format Error:</span>"
									+		"<span class=\"errormessagebody\">"	+	"	More than	one	color	in \"" + match.Groups[0].Value + "\" " + "</span>"
									+	"</span>"
									;
							}
							else
							{
								insideSpan = true;
								newValue +=	"<span style=\"color:"	+	match.Groups[i].Value.ToLower()	+	"\">";
							}
						}
					}
				}

				// Just	leave	%% alone if	it is	on the line	by itself
				if ( newValue	!= string.Empty	)
				{
					lastMatchedPosition	=	match.Groups[0].Index;
					str	=	str.Remove(	lastMatchedPosition, match.Groups[0].Length	);
					str	=	str.Insert(	lastMatchedPosition, newValue	);
					lastMatchedPosition	+= newValue.Length;
				}
				else
				{
					lastMatchedPosition	+= 2;
				}
			}

			for	(	;	insideBig	>	0; --insideBig )
			{
				str	+= "</big>";
			}

			for	(	;	insideSmall	>	0; --insideSmall )
			{
				str	+= "</small>";
			}

			if ( insideSpan	)
			{
				str	+= "</span>";
			}

			return str;	//	+	debugString;
		}
		#endregion

		#region SentencePairedDelimiters
		private string SentencePairedDelimiters(string input)
		{
			string str = input;
			// deemphasis
			str = Regex.Replace (str, "`{2}(.*?)`{2}", "<span class=\"Deemphasis\">$1</span>") ;
			// Bold
			str = Regex.Replace (str, "'{3}(.*?)'{3}", "<strong>$1</strong>") ;
			// Italic
			str = Regex.Replace (str, "'{2}(.*?)'{2}", "<em>$1</em>") ;
			return str;
		}
		#endregion

		#region CountOccurrences
		private int CountOccurrences(string lookIn, string lookFor)
		{
			int answer = 0;
			int offset = 0;
			while (true)
			{
				int found = lookIn.IndexOf(lookFor, offset);
				if (found == -1)
					break;
				offset = found + lookFor.Length;
				answer++;
			}
			return answer;
		}
		#endregion

		#region All Possible states
		private abstract class State
		{
			Formatter _Formatter;

			public State(Formatter f)
			{
				_Formatter = f;
			}

			virtual public void Exit()
			{
			}

			virtual public void Enter()
			{
			}

			protected WikiOutput Output
			{
				get
				{
					return _Formatter.Output;
				}
			}

		}


		#region class PreState
		class PreState : State
		{
			public PreState(Formatter f) : base (f)
			{
			}

			override public void Exit()
			{
				Output.WriteClosePreformatted();
			}

			override public void Enter()
			{
				Output.WriteOpenPreformatted();
			}
		}
		#endregion

		#region CurrentState
		private State _CurrentState;

		/// <summary>
		/// Get and set the current state of the state machine; when transitioning, make sure state.Enter() and state.Exit() get called
		/// </summary>
		private State CurrentState
		{
			get
			{
				return _CurrentState;
			}
			set
			{
				if (_CurrentState != null)
					_CurrentState.Exit();
				_CurrentState = value;
				if (_CurrentState != null)
					_CurrentState.Enter();
			}
		}
		#endregion 

		#region class TableState
		class TableState : State
		{
			public TableState(Formatter f) : base (f)
			{
			}

			override public void Exit()
			{
				Output.WriteCloseTable();
			}

			override public void Enter()
			{
			}

			public bool HasBorder;

		}
		#endregion

		#region class NeutralState
		class NeutralState : State
		{
			public NeutralState(Formatter f) : base (f)
			{
			}
		}
		#endregion 

		#region class ListState
		abstract class ListState : State
		{
			protected int listNest = 0;

			abstract public void WriteIndent();
			abstract public void WriteOutdent();

			public ListState(Formatter f) : base (f)
			{
			}

			public void SetNesting(int thisNest)
			{
				if (listNest < thisNest)
				{
					while (listNest < thisNest)
					{
						WriteIndent();
					}
				}
				else if (listNest > thisNest)
				{
					while (listNest > thisNest)
					{
						WriteOutdent();
					}
				}
			}
		}
		#endregion

		#region class UnorderedListState
		class UnorderedListState : ListState
		{
			public UnorderedListState(Formatter f) : base (f)
			{
			}

			override public void WriteIndent()
			{
				Output.WriteOpenUnorderedList();
				listNest++;
			}

			override public void WriteOutdent()
			{
				Output.WriteCloseUnorderedList();
				listNest--;
			}

			override public void Exit()
			{
				SetNesting(0);
			}

			override public void Enter()
			{
				SetNesting(1);
			}
		}
		#endregion 

		#region class OrderedListState
		class OrderedListState : ListState
		{
			public OrderedListState(Formatter f) : base (f)
			{
			}

			override public void WriteIndent()
			{
				Output.WriteOpenOrderedList();
				listNest++;
			}

			override public void WriteOutdent()
			{
				Output.WriteCloseOrderedList();
				listNest--;
			}

			override public void Exit()
			{
				SetNesting(0);
			}

			override public void Enter()
			{
				SetNesting(1);
			}

		}
		#endregion 
		#endregion

		#region IsNextLinePre
		/// <summary>
		/// Answer true if the next line is pre-formatted
		/// </summary>
		/// <returns></returns>
		private bool IsNextLinePre()
		{
			if (_CurrentLineIndex + 1 <  _Source.Count)
			{
				StyledLine line = (StyledLine)_Source[_CurrentLineIndex+1];
				string next = line.Text;
				return next.StartsWith(" ") || Regex.IsMatch(next, "^[ \t]+[^ \t*1]");
			}        
			return false;
		}
		#endregion

		private bool paraOpen = false;
   
		private void EnsureParaOpen()
		{
			if (!paraOpen) { _Output.WriteOpenPara(); paraOpen = true; }
		}
		
		private void EnsureParaClose()
		{
			if (paraOpen) { _Output.WriteClosePara(); paraOpen = false; }
		}

		
		#region Ensure
		/// <summary>
		/// Make sure we're in a particular state and if we aren't create that state and enter it
		/// </summary>
		/// <param name="aType"></param>
		private void Ensure(Type aType)
		{
			if (CurrentState != null && CurrentState.GetType() == aType)
				return;	// already what's requested
			CurrentState = (State)(Activator.CreateInstance(aType, new object[]{this}));	
		}
		#endregion 

		#region class StyledLine
		private class StyledLine
		{
			string _Text;
			LineStyle _Style;

			public StyledLine(string s, LineStyle style)
			{
				_Text = s;
				_Style = style;
			}

			public StyledLine CombinedWith(StyledLine followingLine)
			{
				return new StyledLine(Text + Environment.NewLine + followingLine.Text, CombineStyles(Style, followingLine.Style));
			}

			LineStyle CombineStyles(LineStyle one, LineStyle two)
			{
				if (one == LineStyle.Add || two == LineStyle.Add)
					return LineStyle.Add;
				if (one == LineStyle.Delete || two == LineStyle.Delete)
					return LineStyle.Delete;
				return LineStyle.Unchanged;
			}

			public string Text
			{
				get
				{
					return _Text;
				}
			}

			public LineStyle Style
			{
				get
				{
					return _Style;
				}
			}

		}
		#endregion

		#region Class TableCellInfo
		private class TableCellInfo
		{
			public TableCellInfo()
			{
			}

			public enum AlignOption
			{
				None,
				Left,
				Center,
				Right
			};

			public AlignOption TableAlignment = AlignOption.None;
			public AlignOption CellAlignment = AlignOption.None;
			public bool HasBorder = true;
			public bool IsHighlighted = false;
			public int ColSpan = 1;
			public int RowSpan = 1;
			public bool AllowBreaks = true;
			public int CellWidth = UnspecifiedWidth;
			public int TableWidth = UnspecifiedWidth;
			public string BackgroundColor = null;

			static public int UnspecifiedWidth = -1;

			/// <summary>
			/// Parse the given options and change the options of this object as a side-effect.  Answer null if the parse goes OK or an error message if not.
			/// </summary>
			/// <param name="format"></param>
			/// <returns></returns>
			public string Parse(string format)
			{
				for (int p = 0; p < format.Length; p++)
				{
					char ch = format[p];
					string num;

					switch (ch)
					{
						case '!':
							IsHighlighted = true;
							continue;

						case '+':
							AllowBreaks = false;
							continue;
				
						case ']':
							CellAlignment = AlignOption.Right;
							continue;

						case '[':
							CellAlignment = AlignOption.Left;
							continue;

						case '^':
							CellAlignment = AlignOption.Center;
							continue;

						case 'T':
							if (p + 1 >= format.Length)
								return "Missing option for 'T' in table format";
							p++;
							char ch2 = format[p];
						switch (ch2)
						{
							case '-':
								HasBorder = false;
								continue;

							case ']':
								TableAlignment = AlignOption.Right;
								continue;

							case '[':
								TableAlignment = AlignOption.Left;
								continue;

							case '^':
								TableAlignment = AlignOption.Center;
								continue;

							case 'W':
								num = "";
								if (p + 1 >= format.Length  || !Char.IsDigit(format[p + 1]))
									return "Missing number for table width percentage option 'TW' in table format";
								p++;
								while (p < format.Length && Char.IsDigit(format[p]))
									num += format[p++];
								TableWidth = Int32.Parse(num);
								p--;
								continue;

							default:
								return "Unknown table formatting option: T" + ch2;
						}

						case 'W':
							num = "";
							if (p + 1 >= format.Length  || !Char.IsDigit(format[p + 1]))
								return "Missing number for cell width percentage option 'W' in table format";
							p++;
							while (p < format.Length && Char.IsDigit(format[p]))
								num += format[p++];
							CellWidth = Int32.Parse(num);
							p--;
							continue;
  
						case 'R':
							num = "";
							if (p + 1 >= format.Length || !Char.IsDigit(format[p + 1]))
								return "Missing number for row span option 'R' in table format";
							p++;
							while (p < format.Length && Char.IsDigit(format[p]))
								num += format[p++];
							RowSpan = Int32.Parse(num);
							p--;
							continue;

						case 'C':
							num = "";
							if (p + 1 >= format.Length || !Char.IsDigit(format[p + 1]))
								return "Missing number for column span option 'C' in table format";
							p++;
							while (p < format.Length && Char.IsDigit(format[p]))
								num += format[p++];
							ColSpan = Int32.Parse(num);
							p--;
							continue;

						case '*':
							if (p + 1 >= format.Length)
								return "Missing color for background color option '<>' in table format";
							p++;
							BackgroundColor = "";
							while (p < format.Length && format[p] != '*')
								BackgroundColor += format[p++];
							if (BackgroundColor == "")
								return "Missing color for background color option '**' in table format";
							continue;


						default:
							return "Unknown table formatting option: " + ch;
					}
				}
				return null;
			}
		}
		#endregion

		#region abstract class WikiOutput
		private abstract class WikiOutput
		{
			public static WikiOutput ForFormat(OutputFormat aFormat, WikiOutput parent)
			{
				switch (aFormat)
				{
					case OutputFormat.HTML:
						return new HTMLWikiOutput(parent);
					default:
						throw new Exception("Unsupported output type requested: " + aFormat.ToString());
				}
			}

			WikiOutput _Parent;

			public bool IsNested
			{
				get
				{
					return _Parent != null;
				}
			}

			public int GetNestingLevel() 
			{
				if (_Parent != null) 
				{
					return 1 + _Parent.GetNestingLevel();
				} 
				else 
				{
					return 0;
				}
			}

			public WikiOutput(WikiOutput parent)
			{
				_TextWriter = new StringWriter();
				_Parent = parent;
			}

			public void Write(string s)
			{
				TextWriter().Write(s);
			}
			public void WriteLine(string s)
			{
				TextWriter().WriteLine(s);
			}

			public TextWriter TextWriter()
			{
				return _TextWriter;
			}

			override public string ToString()
			{
				return TextWriter().ToString();
			}

			abstract public void Begin();
			abstract public void End();
			abstract public void NonBreakingSpace();
			abstract public void AddToFooter(string s);
			abstract public void WriteErrorMessage(string title, string body);
			abstract public void WriteOpenProperty(string name);
			abstract public void WriteCloseProperty();
			abstract public void WriteOpenAnchor(string name);
			abstract public void WriteCloseAnchor();
			abstract public void WriteCloseUnorderedList();
			abstract public void WriteOpenUnorderedList();
			abstract public void WriteCloseOrderedList();
			abstract public void WriteOpenOrderedList();
			abstract public void WriteOpenPreformatted();
			abstract public void WriteRule();
			abstract public void WriteClosePreformatted();
			abstract public void WriteEndLine();
			abstract public void WriteSingleLine(string each);
			abstract public void WriteListItem(string each);
			abstract public void WriteHeading(string text, int level);
			abstract public LineStyle Style {get; set;}
			abstract public void WriteOpenPara();
			abstract public void WriteClosePara();
			abstract public void WriteOpenTable(TableCellInfo.AlignOption alignment, bool hasBorder, int Width);
			abstract public void WriteCloseTable();
			abstract public void WriteOpenTableRow();
			abstract public void WriteCloseTableRow();
			abstract public void WriteTableCell(string s,  bool isHighlighted, TableCellInfo.AlignOption alignment, int colSpan, int RowSpan, bool hasBorder, bool allowBreaks, int Width, string bgcolor);
			abstract public OutputFormat Format { get; }
			abstract public void WriteImage(string title, string URL, string linkToURL, string height, string width, string attributes);
			abstract public void WriteLink(string URL, string tip, string content, string attributes);
			abstract public void WriteLabel(string forId, string text, string attributes);
			//		abstract public void FormStart(string method, string URI, string attributes);
			//		abstract public void FormEnd();
			//		abstract public void FormImageButton(string FieldName, string ImageURI, string TipString, string attributes);
			//		abstract public void FormSubmitButton(string FieldName, string label, string attributes);
			//		abstract public void FormResetButton(string FieldName, string label, string attributes);
			//		abstract public void FormInputBox(string FieldName, string fieldValue, int fieldLength, string attributes);
			//		abstract public void FormCheckbox(string FieldName, string fieldValue, bool isChecked, string attributes);
			//		abstract public void FormRadio(string FieldName, string fieldValue, bool isChecked, string attributes);
			//		abstract public void FormTextarea(string FieldName, string fieldValue, int rows, int cols, string attributes);
			//		abstract public void FormHiddenField(string FieldName, string fieldValue, string attributes);
			//		abstract public void FormSelectField(string fieldName, int size, bool multiple, ArrayList options, string selectedOption, ArrayList values, object selectedValue, string attributes);
			private TextWriter	_TextWriter;
		}
		#endregion

		#region Class LinkMaker
		private class LinkMaker
		{
			private string	_SiteURL;

			public LinkMaker(string siteURL)
			{
				_SiteURL = siteURL;
			}
			public LinkMaker()
			{
			}
			public string SiteURL()
			{
				return _SiteURL;
			}

			public LinkMaker Clone()
			{
				return new LinkMaker(_SiteURL);
			}

		

			public string LinkToUser(string user)
			{
				StringBuilder builder = new StringBuilder();
				builder.Append("<a href=\"mailto:");
				builder.Append(user);
				builder.Append("\">");
				builder.Append(user);
				builder.Append("</a>");			
				return builder.ToString();
			}

		
			public string LinkToImage(string s)
			{
				return SimpleLinkTo(s);
			}

		
			//		public string LinkToSearchNamespace(string ns)
			//		{
			//			return SimpleLinkTo("Search.aspx" + (ns != null ? "?namespace=" + ns : "") );
			//		}

		
			//		public string LinkToSearchFor(string Namespace, string searchExpression)
			//		{
			//			return SimpleLinkTo("Search.aspx" + (Namespace != null ? "?namespace=" + Namespace : "") + (searchExpression != null ? "?search=" + HttpUtility.UrlEncode(searchExpression) : "") );
			//		}

		
			//		public string LinkToVersions(string fullTopicName)
			//		{
			//			return SimpleLinkTo("Versions.aspx" + (fullTopicName != null ? "?topic=" + HttpUtility.UrlEncode(fullTopicName) : "") );
			//		}
			//
			//		
			//		public string LinkToCompare(string fullTopicName, int diff, int oldid)
			//		{
			//			return SimpleLinkTo("Compare.aspx" + (fullTopicName != null ? "?topic=" + HttpUtility.UrlEncode(fullTopicName) : "") + ((diff>=0)?"&diff=" + diff.ToString():string.Empty) + ((oldid>=0)?"&oldid=" + oldid.ToString():string.Empty));
			//		}

			public string LinkToQuicklink()
			{
				return SimpleLinkTo("QuickLink.aspx");
			}

		
			public string LinkToRecentChanges(string ns)
			{
				return SimpleLinkTo("LastModified.aspx"+ (ns != null ? "?namespace=" + ns : "") );
			}

		
			public string LinkToRename(string fullyQualifiedTopicName)
			{
				return SimpleLinkTo("Rename.aspx"+ (fullyQualifiedTopicName != null ? "?topic=" + fullyQualifiedTopicName: "") );
			}

		
			public string LinkToSubscriptions(string ns)
			{
				return SimpleLinkTo("RssFace.aspx"+ (ns != null ? "?namespace=" + ns : "") );
			}

		
			public string LinkToLostAndFound(string ns)
			{
				return SimpleLinkTo("LostAndFound.aspx"+ (ns != null ? "?namespace=" + ns : "") );
			}

		
			public string SimpleLinkTo(string s)
			{
				StringBuilder builder = new StringBuilder();
				builder.Append(SiteURL());
				builder.Append(s);
				return builder.ToString();
			}

		
			//		public string LinkToTopic(string topic)
			//		{
			//			return TopicLink(topic, false, null);
			//		}
			//
			//		
			//		public string LinkToTopicWithDiffs(string topic)
			//		{
			//			return TopicLink(topic, true, null);
			//		}

			//		public string LinkToTopic(TopicName topic)
			//		{
			//			return LinkToTopic(topic, false);
			//		}
			//
			//		public string LinkToTopic(TopicName topic, bool showDiffs)
			//		{
			//			return TopicLink(topic.FullnameWithVersion, showDiffs, null);
			//		}

		
	

			//		public string LinkToLogin(string topic)
			//		{
			//			StringBuilder builder = new StringBuilder();
			//			builder.Append(SiteURL());
			//			builder.Append("Login.aspx?ReturnURL=" + HttpUtility.UrlEncode(TopicLink(topic, false, null)));
			//			return builder.ToString();
			//		}

			//		public string LinkToLogin(TopicName topic)
			//		{
			//			return LinkToLogin(topic.FullnameWithVersion);
			//		}
			//
			//		public string LinkToLogoff(TopicName topic)
			//		{
			//			return LinkToLogoff(topic);
			//		}


			public string LinkToLogoff(string topic)
			{
				StringBuilder builder = new StringBuilder();
				builder.Append(SiteURL());
				builder.Append("Logoff.aspx");
				return builder.ToString();
			}
		
			public string LinkToPrintView(string topic)
			{
				return PrintLink(topic);
			}
			
			//		public string LinkToPrintView(TopicName topic)
			//		{
			//			return PrintLink(topic.FullnameWithVersion);
			//		}

			string PrintLink(string top)
			{
				StringBuilder builder = new StringBuilder();
				builder.Append(SiteURL());
				builder.Append("Print.aspx/");
				builder.Append(top);
				return builder.ToString();
			}

			//		public string LinkToEditTopic(AbsoluteTopicName topic)
			//		{
			//			return EditLink(topic.Fullname);
			//		}

			//		public string LinkToEditTopic(string topic)
			//		{
			//			return EditLink(topic);
			//		}

			//		string EditLink(string top)
			//		{
			//			StringBuilder builder = new StringBuilder();
			//			builder.Append(SiteURL());
			//			builder.Append("WikiEdit.aspx?topic=");
			//			builder.Append(HttpUtility.UrlEncode(top));
			//			if (ReturnToTopicForEditLinks != null)
			//				builder.Append("&return=" + HttpUtility.UrlEncode(ReturnToTopicForEditLinks.Fullname));
			//
			//			return builder.ToString();
			//		}
			//
			//
			//		public string LinkToRestore(string topic)
			//		{
			//			return RestoreLink(topic);
			//		}

			//		public string LinkToRestore(TopicName topic)
			//		{
			//			return RestoreLink(topic.FullnameWithVersion);
			//		}

		
			/// <summary>
			/// Creates the Change User Profile link
			/// </summary>
			/// <param name="topic">TopicName for the link</param>
			/// <returns></returns>
			public string LinkToChangeUserProfile(int userID)
			{
				StringBuilder builder = new StringBuilder();
				builder.Append(SiteURL());
				builder.Append("UserProfile.aspx?UserID=");
				builder.Append(userID.ToString());
				builder.Append("&Mode=2");
				return builder.ToString();
			}
			/// <summary>
			/// Creates the Create User Profile link
			/// </summary>
			/// <param name="topic">TopicName for the link</param>
			/// <returns></returns>
			public string LinkToCreateUserProfile()
			{
				StringBuilder builder = new StringBuilder();
				builder.Append(SiteURL());
				builder.Append("UserProfile.aspx?UserID=0");
				builder.Append("&Mode=1");
				return builder.ToString();
			}

		}
		#endregion

		#region Class HTMLWikiOutput
		private class HTMLWikiOutput : WikiOutput
		{
			LineStyle _CurrentStyle = LineStyle.Unchanged;

			public HTMLWikiOutput(WikiOutput parent) : base(parent)
			{
			}

			string css()
			{
				return css("");
			}

			string css(string extraStyles)
			{
				string styles = extraStyles;
				switch (_CurrentStyle)
				{
					case LineStyle.Add:
						if (styles != "")
							styles += ";";
						styles += "background: palegreen";
						break;

					case LineStyle.Delete:
						if (styles != "")
							styles += ";";
						styles += "color: silver; text-decoration: line-through";
						break;

					case LineStyle.Unchanged:
						break;
				}			
				if (styles.Length > 0)
					styles = " style=\"" + styles + "\"";
				return styles;
			}

			override public void Begin()
			{
				//			if (!IsNested)
				//				WriteScripts();
			}

			//		void WriteScripts()
			//		{
			//			Write(@"
			//<script type=""text/javascript"" language=""javascript"">
			//			function LinkMenu(anArray, e)
			//			{
			//				var src = """";
			//				var h = 0;
			//				var w = 0;
			//
			//				e.cancelBubble = 'true';
			//
			//				
			//				var menu = document.getElementById(""LinkMenu"");
			//				if (menu == null)
			//				{
			//					menu = document.createElement(""div"");
			//					menu.id = 'LinkMenu';
			//					menu.className = 'Menu';
			//					document.body.appendChild(menu);
			//				}
			//				menu.innerHTML = """";
			//				for (var i = 0; i < anArray.length; i++)
			//				{
			//					var pair = anArray[i];
			//					var each = """";
			//					var itemName = 'LinkMenu' + i;
			//					each += '<div class=""MenuItemNormal"" onmouseover=""MenuItemIn(this);"" onmouseout=""MenuItemOut(this);"">';
			//					each += '<span id=""' + itemName + '"" onclick=""MenuClick(' + ""'"" + pair[1] + ""'"" + ')"">' + pair[0] + '<' + '/span>';
			//					each += '<' + '/div>';
			//					menu.innerHTML += each;
			//					var item = document.getElementById(itemName);
			//					if (item.offsetWidth > w)
			//						w = item.offsetWidth;
			//					h += item.offsetHeight;
			//				}
			//				menu.innerHTML = '<div class=""MenuItems"" onmouseover=""MenuIn(this);"" onmouseout=""MenuOut(this);"">' + menu.innerHTML + '<' + '/div>';
			//				menu.style.left = document.body.scrollLeft + e.clientX;
			//				menu.style.top = document.body.scrollTop + e.clientY;
			//				menu.style.height = h;
			//				menu.style.width = w;
			//				timeout = window.setTimeout(""MenuTimeout()"", 4000, ""JavaScript"");
			//				menu.style.display = 'block';
			//			}
			//			
			//			var timeout = null;
			//			var tipOffTimeout = null;
			//
			//			function TopicTipOn(anchor, id, event)
			//			{
			//				var targetY = document.body.scrollTop + event.clientY + 18;
			//				var targetX = document.body.scrollLeft + event.clientX + 12;
			//				
			//				TopicTip.style.left = targetX;
			//				TopicTip.style.top = targetY;
			//				var tip = 	document.getElementById(id);
			//				TopicTip.innerHTML = tip.innerHTML;
			//				TopicTip.style.display = 'block';
			//				if (tipOffTimeout != null)
			//					window.clearTimeout(tipOffTimeout);
			//				tipOffTimeout = window.setTimeout(""TopicTipOff()"", 4000, ""JavaScript"");
			//			}
			//
			//			function TopicTipOff()
			//			{
			//				if (tipOffTimeout != null)
			//					window.clearTimeout(tipOffTimeout);
			//				tipOffTimeout = null;				
			//				TopicTip.style.display = 'none';
			//			}
			//
			//
			//			function MenuClick(url)
			//			{
			//				MenuHide();
			//				document.location.href = url;
			//			}
			//			
			//			function MenuIn(obj)
			//			{
			//				if (timeout == null)
			//					return;
			//				window.clearTimeout(timeout);
			//				timeout = null;
			//			}
			//			
			//			function MenuOut(obj)
			//			{
			//				timeout = window.setTimeout(""MenuTimeout()"", 1000, ""JavaScript"");
			//			}
			//			
			//			function MenuTimeout()
			//			{
			//				MenuHide();
			//			}
			//			
			//			function MenuHide()
			//			{
			//				var menu = document.getElementById(""LinkMenu"");
			//				menu.style.display = 'none';
			//			}
			//
			//			function cleanObfuscatedLink(obj, text, URL)
			//			{
			//				obj.innerText = text;
			//				obj.href = URL;
			//			}
			//
			//			function ShowObfuscatedLink(link)
			//			{
			//				var i = 0;
			//				for (i = 0; i < link.length; i++)
			//				{
			//					document.write(link.substr(i, 1));
			//					if (i < 10)
			//						document.write('..');
			//				}
			//			}
			//			
			//			function MenuItemIn(obj)
			//			{
			//				obj.className = 'MenuItemHover';
			//			}
			//			
			//			function MenuItemOut(obj)
			//			{
			//				obj.className = 'MenuItemNormal';
			//			}
			//</script>	
			//<span style=""display:none"">.</span>
			//");
			//			// not sure why we need the extra span, but we get display bugs on some pages
			//		}

			public override void WriteRule()
			{
				Write("<hr>");
			}


			StringBuilder _Footer = new StringBuilder();
			public override void AddToFooter(string s)
			{
				_Footer.Append(s);
			}

			override  public void End()
			{
				Write(_Footer.ToString());
			}

			override public void WriteClosePara()
			{
				Write("</p>");
			}

			override public void WriteOpenPara()
			{
				Write("<p" + css() + ">");
			}

			override public void WriteSingleLine(string each)
			{
				Write("<div " + css() + ">" + Formatter.EscapeHTML(each)  + "</div>");
			}

			override public void WriteOpenTable(Formatter.TableCellInfo.AlignOption alignment, bool hasBorder, int width)
			{
				string styles = "";
				Write("<table ");
				switch (alignment)
				{
					case TableCellInfo.AlignOption.None:
						break;

					case TableCellInfo.AlignOption.Left:
						styles += ";margin-left: 0; float: left ";
						Write(" align=\"left\" ");
						break;

					case TableCellInfo.AlignOption.Right:
						styles += ";margin-left: 0; float: right";
						break;

					case TableCellInfo.AlignOption.Center:
						Write(" align=\"center\" ");
						break;
				}
				if (width > 0)
					Write(" width=\"" + width + "%\" ");
				string cls = "TableClass";
				if (!hasBorder)
					cls = "TableWithoutBorderClass";
				WriteLine("cellpadding=\"2\" cellspacing=\"1\" class=\"" + cls + "\"" + css(styles) + ">");
			}

			override public void WriteCloseTable()
			{
				WriteLine("</table>");
			}

			override public void WriteOpenTableRow()
			{
				WriteLine("<tr" + css() + ">");
			}

			override public void WriteCloseTableRow()
			{
				Write("</tr>");
			}

			public override void WriteLink(string URL, string tip, string content, string attributes)
			{
				Write("<a href=\"" + URL + "\" ");
				if (tip!= null)
					Write("title=\"" + Formatter.EscapeHTML(tip) + "\"");
				Write(GetAttributeString(attributes));
				Write(">");
				Write(Formatter.EscapeHTML(content));
				Write("</a>");
			}

			override public void WriteLabel(string forId, string text, string attributes)
			{
				Write(string.Format("<label{0} for=\"{1}\">{2}</label>", GetAttributeString(attributes), forId, Formatter.EscapeHTML(text)));
			}

			public override void WriteImage(string title, string URL, string linkToURL, string height, string width, string attributes)
			{
				if (linkToURL != null)
				{
					Write("<a href=\"" + linkToURL + "\" ");
					if (title != null)
						Write("title=\"" + Formatter.EscapeHTML(title) + "\"");
					Write(">");
				}
				Write(string.Format("<img src=\"{0}\" {1}alt=\"{2}\"{3}{4}{5}/>", 
					URL, 
					((linkToURL != null) ? "border=\"0\" " : string.Empty), 
					Formatter.EscapeHTML(title),
					((width != null) ? " width=\"" + width + "\"" : string.Empty ),
					((height != null) ? " height=\"" + height + "\"" : string.Empty ),
					GetAttributeString(attributes)
					));
				if (linkToURL != null)
				{
					Write("</a>");
				}
			}

			public override void NonBreakingSpace()
			{
				Write("&nbsp;");
			}

			public override void WriteErrorMessage(string title, string body)
			{
				Write("<span class=\"ErrorMessage\">");
				if (title != null)
				{
					Write("<span class=\"ErrorMessageTitle\">" + Formatter.EscapeHTML(title));
					if (body != null)
						Write(" ");
					Write("</span>");
				}
				if (body != null)
					Write("<span class=\"ErrorMessageBody\">" + Formatter.EscapeHTML(body) + "</span>");
				Write("</span>");
			}

			override public void WriteTableCell(string s,  bool isHighlighted,Formatter.TableCellInfo.AlignOption alignment, int colSpan, int rowSpan, bool hasBorder, bool allowBreaks, int width, string bgColor)
			{
				Write("<td  ");
				if (isHighlighted)
				{
					if (hasBorder)
						Write("class=\"TableCellHighlighted\"");
					else
						Write("class=\"TableCellHighlightedNoBorder\"");
				}			
				else
				{
					if (hasBorder)
						Write("class=\"TableCell\"");
					else
						Write("class=\"TableCellNoBorder\"");
				}

				if (bgColor != null)
					Write(" style=\" background: " + Formatter.EscapeHTML(bgColor) + "\" ");

				if (!allowBreaks)
					Write(" nowrap ");
				if (width > 0)
					Write(" width=\"" + width + "%\" ");

				switch (alignment)
				{
					case TableCellInfo.AlignOption.None:
						break;

					case TableCellInfo.AlignOption.Left:
						Write(" align=\"left\" ");
						break;

					case TableCellInfo.AlignOption.Right:
						Write(" align=\"right\" ");
						break;

					case TableCellInfo.AlignOption.Center:
						Write(" align=\"center\" ");
						break;
				}

				if (colSpan != 1)
					Write(" colspan=\"" + colSpan + "\" ");
				if (rowSpan != 1)
					Write(" rowspan=\"" + rowSpan + "\" ");
			
				WriteLine(css() + ">" + s + "</td>");
			}

			override public void WriteOpenUnorderedList()
			{
				WriteLine("<ul" + css() + ">");
			}

			override public void WriteCloseUnorderedList()
			{
				WriteLine("</ul>");
			}

			override public void WriteCloseOrderedList()
			{
				WriteLine("</ol>");
			}

			override public void WriteOpenOrderedList()
			{
				WriteLine("<ol" + css() + ">");
			}

			override public void WriteOpenPreformatted()
			{
				WriteLine("<pre" + css() + ">");
			}

			override public void WriteClosePreformatted()
			{
				WriteLine("</pre>");
			}
	
			override public void WriteEndLine()
			{
				WriteLine("");
			}

			override public LineStyle Style 
			{
				get
				{
					return _CurrentStyle;
				}
				set
				{
					_CurrentStyle = value;
				}
			}

			override public void WriteListItem(string each)
			{
				WriteLine("<li" + css() + ">" + each + "</li>");
			}

			override public OutputFormat Format 
			{ 
				get 
				{
					return OutputFormat.HTML;
				}
			}

			override public void WriteHeading(string text, int level)
			{
				WriteLine("<h" + level  + css() + ">" + text + "</h" + level + ">");
			}

			override public void WriteOpenProperty(string name)
			{
				Write("<fieldset " + css() + " class=\"Property\" style=\"width: auto\">");
				Write("<legend class=\"PropertyName\">" + name + "</legend>");
				Write("<div class=\"PropertyValue\">");
			}

			override public void WriteCloseProperty()
			{
				WriteLine("</div>");
				WriteLine("</fieldset>");
			}

			override public void WriteOpenAnchor(string name)
			{
				Write("<a name=\"" + name + "\" class=\"Anchor\">");
			}

			override public void WriteCloseAnchor()
			{
				Write("</a>");
			}
	
			//		override public void FormStart(string method, string URI, string attributes)
			//		{
			//			WriteLine(string.Format("<form action=\"{0}\" method=\"{1}\" {2}>", URI, method, GetAttributeString(attributes)));
			//		}
			//		override public void FormEnd()
			//		{
			//			WriteLine("</form>");
			//		}
			//		override public void FormImageButton(string FieldName, string ImageURI, string TipString, string attributes)
			//		{
			//			Write("<input type=\"image\" src=\"" + ImageURI + "\"" + GetAttributeString(attributes) + " title=\"" + Formatter.EscapeHTML(TipString) + "\" />");
			//		}
			//
			//		override public void FormSubmitButton(string FieldName, string label, string attributes)
			//		{
			//			Write("<input type=\"submit\" name=\"" + FieldName + "\"" + GetAttributeString(attributes) + " value=\"" + Formatter.EscapeHTML(label) + "\" />");
			//		}
			//
			//		override public void FormResetButton(string FieldName, string label, string attributes)
			//		{
			//			Write("<input type=\"reset\" name=\"" + FieldName + "\"" + GetAttributeString(attributes) + " value=\"" + Formatter.EscapeHTML(label) + "\" />");
			//		}
			//
			//		override public void FormInputBox(string FieldName, string fieldValue, int fieldLength, string attributes)
			//		{
			//			Write("<input type=\"text\" name=\"" + FieldName + "\" id=\"" + FieldName + "\"");
			//			Write((fieldLength>0)?" size=\"" + fieldLength.ToString() + "\"":string.Empty);
			//			Write(GetAttributeString(attributes));
			//			Write(" value=\"" + Formatter.EscapeHTML(fieldValue) + "\" />");
			//		}
			//
			//		override public void FormTextarea(string FieldName, string fieldValue, int rows, int cols, string attributes)
			//		{
			//			
			//			Write("<textarea name=\"" + FieldName + "\"" 
			//				+ ((rows>0)?" rows=\"" + rows.ToString() + "\"" : string.Empty)
			//				+ ((cols>0)?" cols=\"" + cols.ToString() + "\"" : string.Empty) 
			//				+ GetAttributeString(attributes)
			//				+ ">"
			//				+ Formatter.EscapeHTML(fieldValue) 
			//				+ "</textarea>");
			//		}
			//
			//		override public void FormCheckbox(string fieldName, string fieldValue, bool isChecked, string attributes)
			//		{
			//			Write("<input type=\"checkbox\" name=\"" + fieldName + "\" value=\"" + Formatter.EscapeHTML(fieldValue) + "\"" + GetAttributeString(attributes) + ((isChecked)?" checked=\"true\"" : string.Empty) + "/>");
			//		}
			//		override public void FormRadio(string fieldName, string fieldValue, bool isChecked, string attributes)
			//		{
			//			Write("<input type=\"radio\" name=\"" + fieldName + "\" value=\"" + Formatter.EscapeHTML(fieldValue) + "\"" + GetAttributeString(attributes) + ((isChecked)?" checked=\"true\" " : string.Empty) + "/>");
			//		}
			//		
			//		override public void FormHiddenField(string FieldName, string fieldValue, string attributes)
			//		{
			//			Write("<input style=\"display: none\" type=\"text\" name=\"" + FieldName + "\" value=\"" + Formatter.EscapeHTML(fieldValue) + "\"" + GetAttributeString(attributes) + " />");
			//		}
			//
			//		public override void FormSelectField(string fieldName, int size, bool multiple, ArrayList options, string selectedOption, ArrayList values, object selectedValue, string attributes)
			//		{
			//			StringBuilder stringBuilder = new StringBuilder();
			//			// Start the select field.
			//			stringBuilder.Append(string.Format("<select name=\"{0}\" id=\"{0}\" size=\"{1}\"", fieldName, size));
			//			// Add the multiple attribute if required.
			//			if (true == multiple)
			//			{
			//				stringBuilder.Append(" multiple=\"multiple\"");
			//			}
			//			stringBuilder.Append(GetAttributeString(attributes));
			//			stringBuilder.Append(">"); // End the select element.
			//			// Add the options.
			//			if (null != values)
			//			{
			//				for (int i = 0; i < options.Count; i++)
			//				{
			//					stringBuilder.Append(string.Format("<option value=\"{0}\"",
			//						values[i].ToString()));
			//					if ((null != selectedValue) && (selectedValue.ToString().Length > 0) && (selectedValue.ToString() == values[i].ToString()))
			//					{
			//						stringBuilder.Append(" selected=\"selected\"");
			//					}
			//					stringBuilder.Append(string.Format(">{0}</option>",
			//						options[i].ToString()));
			//				}
			//			}
			//			else
			//			{
			//				foreach (object option in options)
			//				{
			//					stringBuilder.Append("<option");
			//					if ((null != selectedOption) && (selectedOption.Length > 0) && (selectedOption == option.ToString()))
			//					{
			//						stringBuilder.Append(" selected=\"selected\"");
			//					}
			//					stringBuilder.Append(string.Format(">{0}</option>", option.ToString()));
			//				}
			//			}
			//			// Close the select element.
			//			stringBuilder.Append("</select>");
			//			string selectField = stringBuilder.ToString();
			//			Write(selectField);
			//		}

			private string GetAttributeString (string attr)
			{
				if (attr == null || attr.IndexOf(">") > 0)
					return string.Empty;
		
				return " " + attr;
			}
		}
		#endregion

		#region Enums
		private enum LineStyle
		{
			Add,
			Delete,
			Unchanged
		};
		private enum OutputFormat
		{
			/* 
			Text,
			XAML,
			Wiki, // ?????
			*/
			HTML,
			Testing
		}
		#endregion
	}
}
